package cn.jbt.api.admin.service.impl;

import cn.jbt.api.admin.entity.QXxlJobInfo;
import cn.jbt.api.admin.entity.XxlJobInfo;
import cn.jbt.api.admin.repository.XxlJobGroupRepository;
import cn.jbt.api.admin.repository.XxlJobInfoRepository;
import cn.jbt.api.admin.repository.XxlJobLogRepository;
import cn.jbt.api.admin.repository.XxlJobLogglueRepository;
import cn.jbt.api.admin.service.ISysConfigService;
import cn.jbt.api.admin.service.IXxlJobInfoService;
import cn.jbt.api.admin.util.XxlJobUtil;
import cn.jbt.common.PageQryParam;
import cn.jbt.common.PageQryResult;
import cn.jbt.common.util.CronExpression;
import cn.jbt.config.XxlJobConfig;
import com.alibaba.fastjson2.JSONObject;
import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Predicate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import groovy.util.logging.Slf4j;
import jakarta.transaction.Transactional;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.text.SimpleDateFormat;
import java.util.*;


@Service
@AllArgsConstructor
@Slf4j
public class XxlJobInfoServiceImpl implements IXxlJobInfoService {

    private static final Logger log = LoggerFactory.getLogger(XxlJobInfoServiceImpl.class);
    private final JPAQueryFactory jpaQueryFactory;
    private final XxlJobGroupRepository xxlJobGroupRepository;
    private final XxlJobInfoRepository xxlJobInfoRepository;
    private final XxlJobLogRepository xxlJobLogRepository;
    private final XxlJobLogglueRepository xxlJobLogglueRepository;
    private final ISysConfigService iSysConfigService;

    @Override
    public PageQryResult<XxlJobInfo> getAllByPage(PageQryParam<XxlJobInfo> pageQryParam) {
        XxlJobInfo qParam = pageQryParam.getParam();
        QXxlJobInfo qXxlJobInfo = QXxlJobInfo.xxlJobInfo;

        Predicate predicate = qXxlJobInfo.isNotNull().or(qXxlJobInfo.isNull());
        if (null != qParam) {
            if (null != qParam.getJobGroup()) {
                predicate = ExpressionUtils.and(predicate,
                        qXxlJobInfo.jobGroup.eq(qParam.getJobGroup()));
            }
            if (null != qParam.getTriggerStatus()) {
                predicate = ExpressionUtils.and(predicate,
                        qXxlJobInfo.triggerStatus.goe(qParam.getTriggerStatus()));
            }
            if (StringUtils.isNotEmpty(qParam.getJobDesc())) {
                predicate = ExpressionUtils.and(predicate,
                        qXxlJobInfo.jobDesc.contains(qParam.getJobDesc()));
            }
            if (StringUtils.isNotEmpty(qParam.getAuthor())) {
                predicate = ExpressionUtils.and(predicate,
                        qXxlJobInfo.author.contains(qParam.getAuthor()));
            }
        }

        List<XxlJobInfo> list = jpaQueryFactory.select(qXxlJobInfo)
                .from(qXxlJobInfo)
                .where(predicate)
                .orderBy(qXxlJobInfo.id.desc())
                .offset((pageQryParam.getPage() - 1) * pageQryParam.getSize())
                .limit(pageQryParam.getSize())
                .fetch()
                .stream()
                .peek(xxlJobInfo -> {
                    // 最近3次执行时间
                    List<String> nextTriggerTime = new ArrayList<>();
                    try {
                        Date lastTime = new Date();
                        for (int i = 0; i < 3; i++) {
                            lastTime = new CronExpression(xxlJobInfo.getScheduleConf()).getNextValidTimeAfter(lastTime);
                            nextTriggerTime.add(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(lastTime));
                        }
                        xxlJobInfo.setNextTriggerTime(nextTriggerTime);
                    } catch (Exception ignored) {

                    }

                    Optional.ofNullable(xxlJobGroupRepository.findFirstById(xxlJobInfo.getJobGroup()))
                            .ifPresent(xxlJobInfo::setXxlJobGroup);
                })
                .toList();

        long total = Optional.ofNullable(jpaQueryFactory.select(qXxlJobInfo.id.count())
                .from(qXxlJobInfo)
                .where(predicate)
                .fetchOne()
        ).orElse(0L);

        return new PageQryResult<>(list, total);
    }

    @Override
    public XxlJobInfo add(XxlJobInfo xxlJobInfo) {
        Optional.ofNullable(xxlJobGroupRepository.findFirstById(xxlJobInfo.getJobGroup()))
                .orElseThrow(() -> new RuntimeException("执行器不存在"));

        if (!CronExpression.isValidExpression(xxlJobInfo.getScheduleConf())) {
            throw new RuntimeException("调度配置格式错误");
        }

        xxlJobInfo.setAddTime(new Date());
        xxlJobInfo.setUpdateTime(xxlJobInfo.getAddTime());
        xxlJobInfo.setGlueUpdatetime(xxlJobInfo.getAddTime());
        return xxlJobInfoRepository.save(xxlJobInfo);
    }

    @Override
    public XxlJobInfo edit(XxlJobInfo xxlJobInfo) {
        XxlJobInfo newXxlJobInfo = xxlJobInfoRepository.findFirstById(xxlJobInfo.getId());
        if (null == newXxlJobInfo) {
            throw new RuntimeException("404 NotFound");
        }

        Optional.ofNullable(xxlJobGroupRepository.findFirstById(xxlJobInfo.getJobGroup()))
                .orElseThrow(() -> new RuntimeException("执行器不存在"));

        if (!CronExpression.isValidExpression(xxlJobInfo.getScheduleConf())) {
            throw new RuntimeException("调度配置格式错误");
        }

        // next trigger time (5s后生效，避开预读周期)
        long nextTriggerTime = newXxlJobInfo.getTriggerNextTime();
        if (newXxlJobInfo.getTriggerStatus() == 1 && !xxlJobInfo.getScheduleConf().equals(newXxlJobInfo.getScheduleConf())) {
            try {
                Date nextValidTime = new CronExpression(xxlJobInfo.getScheduleConf()).getNextValidTimeAfter(new Date(System.currentTimeMillis() + 5000));
                nextTriggerTime = nextValidTime.getTime();
            } catch (Exception ignored) {

            }
        }

        newXxlJobInfo.setTriggerNextTime(nextTriggerTime);
        newXxlJobInfo.setUpdateTime(new Date());
        newXxlJobInfo.edit(xxlJobInfo);
        return xxlJobInfoRepository.save(newXxlJobInfo);
    }

    @Transactional(rollbackOn = Exception.class)
    @Override
    public void del(List<Integer> ids) {
        for (Integer id : ids) {
            if (null == xxlJobInfoRepository.findFirstById(id)) {
                throw new RuntimeException("404 NotFound");
            }
        }

        xxlJobInfoRepository.deleteAllByIdInBatch(ids);
        xxlJobLogRepository.deleteByJobIdIn(ids);
        xxlJobLogglueRepository.deleteByJobIdIn(ids);
    }

    @Override
    public void start(int id) {
        XxlJobInfo xxlJobInfo = xxlJobInfoRepository.findFirstById(id);
        if (null == xxlJobInfo) {
            throw new RuntimeException("404 NotFound");
        }

        // next trigger time (5s后生效，避开预读周期)
        long nextTriggerTime = 0;
        try {
            Date nextValidTime = new CronExpression(xxlJobInfo.getScheduleConf()).getNextValidTimeAfter(new Date(System.currentTimeMillis() + 5000));
            nextTriggerTime = nextValidTime.getTime();
        } catch (Exception ignored) {

        }

        xxlJobInfo.setTriggerStatus(1);
        xxlJobInfo.setTriggerLastTime(0L);
        xxlJobInfo.setTriggerNextTime(nextTriggerTime);
        xxlJobInfo.setUpdateTime(new Date());
        xxlJobInfoRepository.save(xxlJobInfo);
    }

    @Override
    public void stop(int id) {
        XxlJobInfo xxlJobInfo = xxlJobInfoRepository.findFirstById(id);
        if (null == xxlJobInfo) {
            throw new RuntimeException("404 NotFound");
        }

        xxlJobInfo.setTriggerStatus(0);
        xxlJobInfo.setTriggerLastTime(0L);
        xxlJobInfo.setTriggerNextTime(0L);
        xxlJobInfo.setUpdateTime(new Date());
        xxlJobInfoRepository.save(xxlJobInfo);
    }

    @Override
    public void trigger(int id, String executorParam, String addressList) {
        XxlJobConfig xxlJobConfig = (XxlJobConfig) iSysConfigService.getConfig("XxlJob");

        XxlJobInfo xxlJobInfo = xxlJobInfoRepository.findFirstById(id);
        if (null == xxlJobInfo) {
            throw new RuntimeException("404 NotFound");
        }

        XxlJobUtil xxlJobUtil = new XxlJobUtil()
                .setXxlJobConfig(xxlJobConfig);

        List<String> cookies = xxlJobUtil.getCookiesByLogin();
        log.info("cookies: {}", cookies);

        RestTemplate restTemplate = new RestTemplate();
        // headers
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.put(HttpHeaders.COOKIE, cookies);
        // form variables
        MultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
        map.add("id", id);
        map.add("executorParam", executorParam);
        map.add("addressList", addressList);
        // request
        HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(map, headers);
        // response
        ResponseEntity<String> response = restTemplate.postForEntity(
                String.format("%s/jobinfo/trigger", xxlJobConfig.getAdminAddresses()),
                request, String.class);
        if (!response.getStatusCode().equals(HttpStatus.OK)) {
            throw new RuntimeException("请求错误：" + response.getStatusCode());
        }

        log.info("getBody: {}", response.getBody());
        JSONObject result = JSONObject.parseObject(response.getBody());
        assert result != null;

        if (result.getInteger("code") != 200) {
            throw new RuntimeException(result.getString("msg"));
        }
    }
}
